目前已經透過 OpenWeatherMap API 拿到現在的天氣資料,但是要怎麼將資料傳遞給 component 使用呢?
有以下幾種方式可以傳遞資料給 component:
| 傳遞方式 | 優點 | 缺點 | 
|---|---|---|
| props | 最簡單使用 | 需要層層傳遞,難以維護 | 
| Context | 避免層層傳遞的問題 | 重新渲染效能可能受影響,狀態龐大也不好維護 | 
| Redux | 狀態可預測,強大的開發工具及龐大的生態系 | 對於簡單的專案可能過於複雜 | 
| Zustand | 簡潔靈活的 API 與無需使用 reducer | 相比 Redux 使用人數較少 | 
方法有很多種,不過綜合考量後,這篇文章將會使用 Zustand 來管理狀態。(因為我想用新玩具來玩,不然平常是用 Redux Toolkit)
那麼本篇文章將會示範如何使用 Zustand 這個狀態管理套件,來幫助我們解決這個問題。
首先先安裝 Zustand:
$ npm install zustand
接下來在 src 資料夾下新增一個 store 資料夾,並且新增一個 index.ts 檔案:
import { create } from 'zustand'
import * as Location from 'expo-location'
interface WeatherDetail {
  description: string
  icon: string
  id: number
  main: string
}
interface CurrentWeatherInfo {
  feels_like: number
  temp: number
  weather: WeatherDetail[]
}
interface Store {
  location: Location.LocationObject | null
  errorMsg: string | null
  currentWeather: CurrentWeatherInfo | null
  setLocation: (location: Location.LocationObject | null) => void
  setErrorMsg: (msg: string | null) => void
  setCurrentWeather: (weather: CurrentWeatherInfo | null) => void
  fetchWeatherData: () => Promise<void>
}
使用 Zustand 的起手式是使用 create 來建立一個 store,並且定義 store 中的狀態與方法,這邊定義了 location、errorMsg、currentWeather 這三個狀態,並且定義了 setLocation、setErrorMsg、setCurrentWeather、fetchWeatherData 這四個方法。
Zustand 跟 Redux 的差別在於,Zustand 的狀態跟方法都是在同一個物件中,而且不需要使用 reducer 來更新狀態,只需要直接使用 setXXX 這種方式就可以更新狀態。
接下來使用 create 來建立 store:
const useStore = create<Store>((set) => ({
  location: null,
  errorMsg: null,
  currentWeather: null,
  setLocation: (location) => set({ location }),
  setErrorMsg: (msg) => set({ errorMsg: msg }),
  setCurrentWeather: (weather) => set({ currentWeather: weather }),
  fetchWeatherData: async () => {
    const { location, errorMsg } = useStore.getState()
    if (location && location.coords) {
      const { latitude, longitude } = location.coords
      const apiUrl = process.env.EXPO_PUBLIC_WEATHER_API_URL
      const apiKey = process.env.EXPO_PUBLIC_WEATHER_API_KEY
      try {
        const response = await fetch(
          `${apiUrl}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&units=metric&appid=${apiKey}`
        )
        const json = await response.json()
        set({ currentWeather: json.current })
      } catch (err) {
        console.log(err)
      }
    }
  }
}))
export default useStore
create 的第一個參數是一個 callback,callback 中的 set 參數是一個函式,可以用來更新狀態,而 create 的回傳值就是一個 hook,可以直接使用。
這裡如同上面所提到的,會使用 set 來更新狀態,而 set 的參數是一個物件,物件中的 key 就是狀態的名稱,value 則是狀態的值。最後再把 useStore export 出去,就可以在其他地方使用了。
接下來就可以在 App.tsx 中使用 useStore 了,並且可以把 useState 全部刪掉,用不到了:
import { useEffect } from 'react'
import * as Location from 'expo-location'
import useStore from './src/store'
export default function App() {
  const { location, setLocation, setErrorMsg, fetchWeatherData } = useStore()
  useEffect(() => {
    ;(async () => {
      let { status } = await Location.requestForegroundPermissionsAsync()
      if (status !== 'granted') {
        setErrorMsg('Permission to access location was denied')
        return
      }
      let location = await Location.getCurrentPositionAsync({})
      setLocation(location)
    })()
  }, [])
  useEffect(() => {
    if (location) {
      fetchWeatherData()
    }
  }, [location])
  return (
    <SafeAreaView className='flex-1 bg-blue-500'>
      // 省略
    </SafeAreaView>
  )
}
這裡可以看到,我們可以直接使用 useStore 來取得狀態與方法,並且可以直接使用剛剛定義的 setLocation 來更新 GPS 定位狀態,並且使用 fetchWeatherData 來取得天氣資料。
拿到目前天氣資料之後,可以直接在 CurrentWeather.tsx 中使用 useStore 來顯示天氣資料:
import { View, Text } from 'react-native'
import { AntDesign } from '@expo/vector-icons'
import useStore from '../store'
const CurrentWeather = () => {
  const { currentWeather } = useStore()
  if (
    !currentWeather ||
    !currentWeather?.weather ||
    currentWeather?.weather?.length === 0
  ) {
    return null
  }
  const { temp } = currentWeather
  const { description } = currentWeather?.weather[0]
  return (
    <View className='items-center space-y-2'>
      <AntDesign name='cloudo' size={60} color='#ded8d8' />
      <Text className='text-6xl font-bold text-white'>{temp}</Text>
      <Text className='text-xl text-gray-300'>{description}</Text>
    </View>
  )
}
在 CurrentWeather 中,也一樣直接使用 useStore 來取得剛剛取得的天氣資料 currentWeather,並且使用顯示。

透過使用 Zustand,可以讓我們更簡單地管理狀態,其簡單直覺的 API 設計,讓我們可以更快速地開發應用程式,如果之後有開發的專案有狀態管理的需求,或許可以考慮使用 Zustand 來管理狀態。